//
// Created by YE on 2024/1/15.
//

#include "CFunction.h"
#include <cmath>

static Float **malloc_mat(unsigned int m, unsigned int n) {
    Float **mat = new Float *[m];
    for (unsigned int i = 0; i < m; i++) {
        *(mat + i) = new Float[n];
    }
    return mat;
}

static void delete_mat(Mat *mat) {
    for (unsigned int i = 0; i < mat->m; i++) {
        delete mat->mat[i];
    }
    delete mat->mat;
    delete mat;
}

static Mat *copy_mat(Mat *old_m) {
    Mat *new_mat = new Mat({old_m->m, old_m->n});
    new_mat->mat = malloc_mat(new_mat->m, new_mat->n);
    for (unsigned int i = 0; i < old_m->m; i++)
        for (unsigned int j = 0; j < old_m->n; j++)
            new_mat->mat[i][j] = old_m->mat[i][j];
    return new_mat;
}

void gaussianElimination(Mat *A, Mat *B, Mat *X) {
    int i, j, k;
    double factor;
    int Am=A->m,An=A->n,Bn=B->n;
    for (i = 0; i < Am - 1; i++) {
        for (j = i + 1; j < Am; j++) {
            factor = A->mat[j][i] / A->mat[i][i];
            for (k = i; k < An; k++) {
                A->mat[j][k] -= factor * A->mat[i][k];
            }
            for (k = 0; k < Bn; k++) {
                B->mat[j][k] -= factor * B->mat[i][k];
            }
        }
    }

    for (k = 0; k < Bn; k++) {
        for (i =An- 1; i >= 0; i--) {
            X->mat[i][k] = B->mat[i][k];
            for (j = i + 1; j < An; j++) {
                X->mat[i][k] -= A->mat[i][j] * X->mat[j][k];
            }
            X->mat[i][k] /= A->mat[i][i];
        }
    }
}

void C_print(Object *object1, Object *object2) {
    switch (object1->t) {
        case Ot_Int:
            printf("%lld", object1->v.i);
            break;
        case Ot_Float:
            printf("%f", object1->v.f);
            break;
        case Ot_Bool:
            if (object1->v.b) {
                printf("true");
            } else {
                printf("false");
            }
            break;
        case Ot_Mat:
            printf("[");
            for (unsigned int i = 0; i < object1->v.mat->m; i++) {
                printf("[");
                for (unsigned int j = 0; j < object1->v.mat->n; j++) {
                    printf("%.3f ", object1->v.mat->mat[i][j]);
                }
                if (i == object1->v.mat->m - 1) {
                    printf("]");
                } else {
                    printf("]\n");
                }
            }
            printf("]");
            break;
        case Ot_String:
            if (object1->v.str->data)
                printf("%s", object1->v.str->data);
            else {
                printf("None");
            }
            break;
        default:
            printf("None");
    }
    if (object2->v.b)
        printf("\n");
}

Object C_sin(Object *object1) {
    Object o{};

    switch (object1->t) {
        case Ot_Int:
            o.v.f = sin(object1->v.i);
            o.t = Ot_Float;
            break;
        case Ot_Float:
            o.v.f = sin(object1->v.f);
            o.t = Ot_Float;
            break;
        default:
            o.t = Ot_None;
    }
    return o;
}

Object C_getMatXY(Object *object1, Object *object2, Object *object3) {
    Object o{};
    if (object1->t == Ot_Mat && object2->t == Ot_Int && object3->t == Ot_Int) {
        if (object2->v.i < object1->v.mat->m && object3->v.i < object1->v.mat->n) {
            o.v.f = object1->v.mat->mat[object2->v.i][object3->v.i];
            o.t = Ot_Float;
        }
    }
    return o;
}

void C_setMatXY(Object *object1, Object *object2, Object *object3, Object *object4) {
    if (object1->t == Ot_Mat && object2->t == Ot_Int && object3->t == Ot_Int) {
        if (object2->v.i < object1->v.mat->m && object3->v.i < object1->v.mat->n) {
            if (object4->t == Ot_Int)
                object1->v.mat->mat[object2->v.i][object3->v.i] = object4->v.i;
            else if (object4->t == Ot_Float)
                object1->v.mat->mat[object2->v.i][object3->v.i] = object4->v.f;
        }
    }
}

Object C_createMat(Object *object1, Object *object2, Object *object3, Object *object4) {
    Object o{};
    Float v;
    if (object1->t != Ot_Int || object2->t != Ot_Int || object4->t != Ot_Int) {
        return o;
    }
    if (object3->t == Ot_Int) {
        v = object3->v.i;
    } else if (object3->t == Ot_Float) {
        v = object3->v.f;
    } else {
        return o;
    }

    if (object1->v.i <= 0 || object2->v.i <= 0) {
        return o;
    }
    unsigned int t = object4->v.i;
    unsigned int m = object1->v.i, n = object2->v.i;
    Float **mat = malloc_mat(m, n);
    if (t == 1) {
        for (unsigned int i = 0; i < m; i++)
            for (unsigned int j = 0; j < n; j++)
                if (i == j)
                    mat[i][j] = v;
                else
                    mat[i][j] = 0;
    } else if (t == 2) {
        for (unsigned int i = 0; i < m; i++)
            for (unsigned int j = 0; j < n; j++)
                mat[i][j] = v;
    }
    o.t = Ot_Mat;
    o.v.mat = new Mat({m, n, mat});
    return o;
}

Object C_solveMatE(Object *object1, Object *object2) {
    Object o{};
    if (object1->t != Ot_Mat || object2->t != Ot_Mat) {
        return o;
    }
    o.t = Ot_Mat;
    o.v.mat = new Mat({object1->v.mat->n, object2->v.mat->n, malloc_mat(object1->v.mat->n, object2->v.mat->n)});
    Mat *a = copy_mat(object1->v.mat);
    Mat *b = copy_mat(object2->v.mat);
    gaussianElimination(a, b, o.v.mat);
    delete_mat(a);
    delete_mat(b);
    return o;
}

void C_YCH() {
    printf("  *   *     *****   *       *\n   * *     *        *       *\n    *     *         * * * * *\n"
           "    *      *        *       *\n    *       *****   *       *");
}

const CFunc_Info CFuncInfos[CFuncSIZE] = {
        {"print",     2, (void *) &C_print,     CFT_V2},
        {"sin",       1, (void *) &C_sin,       CFT_O1},
        {"ych",       0, (void *) &C_YCH,       CFT_V0},
        {"getMatXY",  3, (void *) &C_getMatXY,  CFT_O3},
        {"setMatXY",  4, (void *) &C_setMatXY,  CFT_V4},
        {"createMat", 4, (void *) &C_createMat, CFT_O4},
        {"solveMatE",2,(void *)&C_solveMatE,CFT_O2},
};